Skip to content

fix: resolve ABI generation failure for contracts with unguarded #[no_mangle] functions#401

Draft
r-near wants to merge 1 commit intomainfrom
fix/abi-unguarded-no-mangle
Draft

fix: resolve ABI generation failure for contracts with unguarded #[no_mangle] functions#401
r-near wants to merge 1 commit intomainfrom
fix/abi-unguarded-no-mangle

Conversation

@r-near
Copy link
Contributor

@r-near r-near commented Feb 18, 2026

Summary

Fixes #317. Supersedes #384.

Contracts with hand-written #[no_mangle] functions that reference NEAR host imports (e.g. input, storage_write) fail during ABI generation because those symbols only exist in the wasm runtime, not on the native host.

This fixes the problem in the tooling so contract authors don't need to add #[cfg] guards to their code:

  • On all unix platforms, a small shim library with no-op stubs for NEAR host functions is compiled on the fly and pre-loaded with RTLD_GLOBAL before dlopen'ing the contract dylib, so the dynamic linker can resolve the missing symbols
  • On macOS, the native linker also rejects undefined symbols in shared libraries, so -undefined dynamic_lookup is passed via RUSTFLAGS during the ABI generation step only
  • No changes to compile.rs — the fix is fully scoped to the ABI generation path

Test plan

  • New integration test test_abi_with_unguarded_no_mangle_function — verifies ABI extraction succeeds for a contract with an unguarded #[no_mangle] fn calling near_sdk::sys::input()
  • New integration test test_build_with_unguarded_no_mangle_function — verifies full build + deploy + call succeeds for the same contract
  • Verify against the original reproducer from [Bug] ABI generation fails #317 (house-of-stake-contracts venear-contract)

…_mangle] functions

Contracts with hand-written #[no_mangle] functions that call NEAR host
imports (e.g. input, storage_write) fail during ABI generation because
the native dylib build cannot resolve these wasm-only symbols.

This fixes the problem in the tooling so contract authors don't need to
add #[cfg] guards to their code:

- On all unix platforms, a small shim library with no-op stubs for NEAR
  host functions is compiled on the fly and pre-loaded with RTLD_GLOBAL
  before dlopen'ing the contract dylib, allowing symbol resolution.

- On macOS, the linker also rejects undefined symbols in shared
  libraries, so -undefined dynamic_lookup is passed via RUSTFLAGS
  during the ABI generation step only.

Closes #317
Copilot AI review requested due to automatic review settings February 18, 2026 21:19
@r-near r-near requested a review from a team as a code owner February 18, 2026 21:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR fixes ABI generation failures for contracts that include unguarded #[no_mangle] functions referencing NEAR host imports by making the ABI-generation tooling able to load such native dylibs successfully on Unix/macOS.

Changes:

  • Add Unix-side preloading of a generated shim dylib that defines NEAR host function symbols so dlopen can resolve them during ABI extraction.
  • Add macOS-only linker flags (-undefined dynamic_lookup) for ABI-generation compilation to allow unresolved host symbols at link time.
  • Add integration tests covering ABI extraction and full build+deploy+call for a contract containing an unguarded #[no_mangle] function calling near_sdk::sys::input.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
cargo-near-build/src/near/abi/generate/mod.rs Inject macOS-specific RUSTFLAGS for ABI-generation compilation to allow undefined symbols.
cargo-near-build/src/near/abi/generate/dylib.rs Preload a generated shim dylib on Unix to satisfy missing NEAR host symbols before dlopen.
integration-tests/tests/abi/e2e.rs Add ABI extraction regression test for unguarded #[no_mangle] host-import usage.
integration-tests/tests/build/opts.rs Add end-to-end build+deploy+view-call test for the same scenario.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +82 to +84
if let Some(rustflags) = abi_generation_rustflags(hide_warnings) {
compile_env.push((env_keys::RUSTFLAGS, rustflags));
hide_warnings_for_compile = false;
Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On macOS this unconditionally pushes a new RUSTFLAGS value into compile_env, which will override any RUSTFLAGS already present in the env slice (because cargo_native::compile::run collects env into a map). This is inconsistent with the WASM build path (where user-provided env vars are allowed to override tool defaults) and can break projects that rely on custom RUSTFLAGS during ABI generation. Consider reading any existing RUSTFLAGS from env and appending -C link-arg=-undefined -C link-arg=dynamic_lookup (and -Awarnings when needed) rather than replacing the value entirely.

Suggested change
if let Some(rustflags) = abi_generation_rustflags(hide_warnings) {
compile_env.push((env_keys::RUSTFLAGS, rustflags));
hide_warnings_for_compile = false;
let has_rustflags_override = env.iter().any(|(key, _)| *key == env_keys::RUSTFLAGS);
if !has_rustflags_override {
if let Some(rustflags) = abi_generation_rustflags(hide_warnings) {
compile_env.push((env_keys::RUSTFLAGS, rustflags));
hide_warnings_for_compile = false;
}

Copilot uses AI. Check for mistakes.
Comment on lines +45 to +51
// User-authored #[no_mangle] functions can reference NEAR host imports.
// Those symbols are unresolved on host and would make dlopen fail before
// we can call __near_abi_* export functions. Load a small shim library
// with no-op definitions so ABI extraction can proceed.
#[cfg(unix)]
let _host_function_stubs = load_near_host_function_stubs()?;

Copy link

Copilot AI Feb 18, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

load_near_host_function_stubs() is executed on every ABI extraction on Unix, which means spawning rustc and compiling a cdylib even when the target dylib has no unresolved host symbols. This adds noticeable overhead to cargo near abi/cargo near build (and to integration tests) and may also complicate environments with restricted process spawning. Consider compiling/loading the stubs lazily only when dlopen fails due to an undefined symbol, or caching the compiled+loaded stubs with a OnceLock so it happens at most once per process.

Copilot uses AI. Check for mistakes.
@r-near r-near marked this pull request as draft February 18, 2026 21:50
@r-near
Copy link
Contributor Author

r-near commented Feb 18, 2026

I need to benchmark this to make sure there's limited performance degradation

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] ABI generation fails

2 participants